import java.sql.*;

import javax.sql.*;

import org.apache.commons.lang.StringEscapeUtils;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

import java.text.DateFormat;
import java.text.SimpleDateFormat;

import java.io.FileWriter;
import java.io.BufferedWriter;
import java.io.IOException;

import java.io.*;
import java.sql.Types;
import java.util.*;
import java.io.File;
import java.sql.Time;

import java.util.regex.*;

import java.lang.*;
import java.util.*;
import java.util.Date;


class Files
{
	//elements
	private String filename;
	private Timestamp date;
	
	final public static long oneDay = 3600 * 24 * 1000;
	
	public Files()
	{		

	}
	
	public void setFilename(String filename)
	{
		this.filename = filename;
	}
	
	public void setDate(Timestamp date)
	{
		this.date = date;
	}
	
	public String getFilename()
	{
		return filename;
	}
	
	public Timestamp getDate()
	{
		return date;
	}
}

//message class and target list
class FileList
{
	PreparedStatement fnps = null;
	
	private int count = 0;
	
	final private static int HoneycombLeft = 1;
	final private static int HoneycombRight = 2;
	
	final private static int V235Left = 3;
	final private static int V235Right = 4;
	
	final private static int middleOfNoWhere = -1;
	
	final public static long dayInterval = 40;
	
	
	final private static String Honeycomb = "Fri Feb 22 14:29:07 2011 -0700";
	final private static String V235 = "Fri Jul 25 14:29:07 2011 -0700";
	
	private long honeycombDate = 0;
	private long V235Date = 0;
	
	private long honeycomLeftBoundary = 0;
	private long honeycomRightBoundary = 0;
	
	private long V235LeftBoundary = 0;
	private long V235RightBoundary = 0;
	
	public FileList(PreparedStatement fnps)
	{
		this.fnps = fnps;
		
		honeycombDate = STBD.toTimestamp(Honeycomb).getTime();
		V235Date = STBD.toTimestamp(V235).getTime();
		
		honeycomLeftBoundary = honeycombDate - dayInterval * 3600 * 24 * 1000;
		honeycomRightBoundary = honeycombDate + dayInterval * 3600 * 24 * 1000;
		
		V235LeftBoundary = V235Date - dayInterval * 3600 * 24 * 1000;
		V235RightBoundary = V235Date + dayInterval * 3600 * 24 * 1000;
	}
	
	//add object to the batch
	public void add(Files file) throws SQLException
	{	
		count++;
		
		//add commit hash
		String filename = file.getFilename();
		if (filename == null)
		{
			return;
		}
		else
		{
			fnps.setString(1, filename);
		}
		
		//add date
		Timestamp date = file.getDate();
		if (date == null)
		{
			fnps.setNull(2, Types.TIMESTAMP);
		}
		else
		{
			fnps.setTimestamp(2, date);
		}
		
		fnps.setInt(3, getLabel(date));
		
		//add to batch
		fnps.addBatch();
		
		//System.out.println("target");
		//System.out.println(line);
		
		if (count > STBD.MULTIPLELINES_BATCH_SIZE)
		{
			count = 0;
			
			outsource();
		}
	}
	
	//push into db
	public void outsource() throws SQLException
	{
		fnps.executeBatch();
	}
	
	public int getLabel(Timestamp date)
	{
		long currentTime = date.getTime();
		
		if ((currentTime >= honeycomLeftBoundary) && (currentTime <= honeycomLeftBoundary))
		{
			if (currentTime <= honeycombDate)
			{
				return HoneycombLeft;
			}
			else
			{
				return HoneycombRight;
			}
		}
		
		if ((currentTime >= V235LeftBoundary) && (currentTime <= V235RightBoundary))
		{
			if (currentTime <= V235Date)
			{
				return V235Left;
			}
			else
			{
				return V235Right;
			}
		}
		
		return middleOfNoWhere;
	}
	
}



public class STBD 
{
	
	class ChangeXMLParser extends DefaultHandler
	{
		//to record current commit hash
		private String commitHash = null;
		
		//object for changebody, message and target
		private ChangeBody cb = null;
		private MultipleLines ml = null;
		
		String elementContent = null;
		
		private int totalEntityNum = 0;
		private int targetEntityNum = 0;
		private int messageEntityNum = 0;
		
		Timestamp date = null;
		
		String content = null;
		
		public ChangeXMLParser()
		{
	    	super();
		}
		
	    public void startDocument()
	    {
	
	    }
	    
	    public void endDocument()
	    {
	    	//push remain items in buffer into db
	    	try
	    	{
	    		fileList.outsource();
	    	}
	    	catch(Exception e)
	    	{
	    		e.printStackTrace();
	    	}
	    	
	    	try {
				db.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	    }
	    
	    private String normalize(String str)
	    {
	    	if (str != null && !str.trim().equalsIgnoreCase("null") 
	    			&& !str.trim().equals(""))
	    	{
	    		str = str.trim();
	    		return StringEscapeUtils.escapeHtml(str);
	    		
	    		//return str;
	    	}
	    	
	    	return null;
	    }
	    
	    public void startElement (String uri, String name, 
	    		String qName, Attributes atts)
	    {	
	    	//get current element's label
	    	Integer currentElement = elementLabelList.get(name.trim());
	    	
	    	elementContent = null;
	   
	    	if (currentElement != null 
	    			&& currentElement >= CHANGE && currentElement <= TARGET) 
	    	{
				switch (currentElement) 
				{
					case CHANGE: 
					{
						//cb = new ChangeBody();
						
						//set it to false, unreadable
						isReadable = false;
						
			    		//elementContent = null;
						
						break;
					}
					case MESSAGE: 
					{			
						isMessage = true;
						
						//ml = new MultipleLines();
						
						//set it to false, unreadable
						isReadable = false;
						
			    		//elementContent = null;
											
						break;
					}
					case TARGET: 
					{
						isTarget = true;
						
						//ml = new MultipleLines();
						
						//set it to false, unreadable
						isReadable = false;
						
			    		//elementContent = null;
						
						break;
					}
					default: 
					{
						if ((currentElement >= PROJECT && currentElement <= LINE))
						{
							isReadable = true;
						
							//elementContent = new String();
						}
						
						break;
					}
				}
			}
	    	else
	    	{
	    		//set it to false, unreadable
	    		isReadable = false;
	    		
	    		elementContent = null;
	    	}
	    }
	    
	    public void endElement (String uri, String name, String qName)
	    {
	    	//get current element's label
	    	Integer currentElement = elementLabelList.get(name.trim());

	    	if(currentElement != null 
	    			&& currentElement >= CHANGE && currentElement <= TARGET)
	    	{
	    		
		    	switch(currentElement)
		    	{
		    		case CHANGE:
		    		{
		    			if (totalEntityNum > 13000)
		    			{
		    				System.err.println(content);
		    				System.err.println("dame hell");
		    			}
		    			
		    			
		    			isMessage = false;
		    			
		    			addMap(totalEntityNum, changeDistributionMap);
		    			totalEntityNum = 0;
		    			messageEntityNum = 0;
		    			targetEntityNum = 0;
		    			
		    			break;
		    		}
		    		case MESSAGE:
		    		{
		    			isMessage = false;
		    			
		    			addMap(messageEntityNum, messageDistributionMap);
		    			messageEntityNum = 0;
		    			
		    			totalEntityNum++;
		 	
		    			break;
		    		}
		    		case TARGET:
		    		{
		    			isTarget = false;
		    			
		    			addMap(targetEntityNum, targetDistributionMap);
		    			targetEntityNum = 0;
		    			
		    			totalEntityNum++;
		    			
		    			break;
		    		}
		    		case LINE:
		    		{	    			
		    			//add it to the list
		    			if (isMessage)
		    			{	
		    				messageEntityNum++;
		    				
		    				totalEntityNum++;
		    			}
		    			else if (isTarget)
		    			{
		    				file.setFilename(elementContent);
		    				file.setDate(date);
		    				
		    				try
		    				{
		    					fileList.add(file);
		    				}
		    				catch(Exception e)
		    				{
		    					e.printStackTrace();
		    				}
		    				
		    				targetEntityNum++;
		    				
		    				totalEntityNum++;
		    			}
		    			
		    			break;
		    		}
		    		case COMMITTERDATE:
		    		{
		    			date = toTimestamp(elementContent);
		    			
		    			file.setDate(date);
		    			
		    			totalEntityNum++;
		    			
		    			break;
		    		}
		    		case COMMITHASH:
		    		{
		    			totalEntityNum++;
		    			
		    			content = elementContent;
		    			
		    			break;
		    		}
		    		default:
		    		{	
		    			if (isReadable)
		    			{
		    				totalEntityNum++;
		    			}
		    			break;
		    		}    	
		    	}
		    	
	    	}
	    	
	    	//reset this flag to make following text unreadable
			isReadable = false;
			
			elementContent= null;
	    }
	    
	    public void characters (char ch[], int start, int length)
	    {
	    	//read in characters when current element is meaningful
	    	if (isReadable == true)
	    	{
	    		//System.out.println(new String(ch, start, length));
	    		
	    		//elementContent += normalize(new String(ch, start, length));
	    		
	    	
	    		if ((elementContent != null) || (length > 0))
	    		{	    		
		    		elementContent = (elementContent == null) 
		    				? normalize(new String(ch, start, length))
		    				: elementContent + normalize(new String(ch, start, length));
	    		}

	    	
	    		//elementContent = normalize(new String(ch, start, length));
	    		//System.out.println(elementContent);
	    	}
	    }
	}
	
	//db object
	DB db = null;
	
	//the number of rows fetched each time
	public static final int MULTIPLELINES_BATCH_SIZE = 500;
	public static final int FILENAME_ROW_LIMIT = 5000;
	
	//for xml filepath
	String filename;
	
	
	//xml reader, used to read in the xml file
	private XMLReader xr = null;
	
	//element labels
	final public static int CHANGE = 0;
	
	final public static int PROJECT = 1;
	final public static int COMMITHASH = 2;
	final public static int TREEHASH = 3;
	final public static int PARENTHASHES = 4;
	final public static int AUTHORNAME = 5;
	final public static int AUTHOREMAIL = 6;
	final public static int AUTHORDATE = 7;
	final public static int COMMITTERNAME = 8;
	final public static int COMMITTEREMAIL = 9;
	final public static int COMMITTERDATE = 10;
	final public static int SUBJECT = 11;
	
	final public static int LINE = 12;
	
	final public static int MESSAGE = 13;
	
	final public static int TARGET = 14;
	
	Files file = null;
	
	//element label list
	public static HashMap<String, Integer> elementLabelList = null;
	static
	{
		elementLabelList = new HashMap<String, Integer>();
		
		elementLabelList.put("change", CHANGE);
		elementLabelList.put("project", PROJECT);
		elementLabelList.put("commit_hash", COMMITHASH);
		elementLabelList.put("tree_hash", TREEHASH);
		elementLabelList.put("parent_hashes", PARENTHASHES);
		elementLabelList.put("author_name", AUTHORNAME);
		elementLabelList.put("author_e-mail", AUTHOREMAIL);
		elementLabelList.put("author_date", AUTHORDATE);
		elementLabelList.put("commiter_name", COMMITTERNAME);
		elementLabelList.put("commiter_email", COMMITTEREMAIL);
		elementLabelList.put("committer_date", COMMITTERDATE);
		elementLabelList.put("subject", SUBJECT);
		elementLabelList.put("message", MESSAGE);
		elementLabelList.put("target", TARGET);
		elementLabelList.put("line", LINE);
	}
	
	//prepared statement
	private PreparedStatement fnps = null;
	
	//list of changebody, message and target
	private FileList fileList = null;
	
	
	//flag for readable characters
	boolean isReadable = false;
	
	boolean isMessage = false;
	boolean isTarget = false;
	
	//for distribution counting
	HashMap<Integer, Integer> changeDistributionMap = null;
	HashMap<Integer, Integer> targetDistributionMap = null;
	HashMap<Integer, Integer> messageDistributionMap = null;
	
	//private HashSet<String> suffix = null;
	//connect to the db

	public STBD(String filename) throws ClassNotFoundException, IllegalAccessException, SQLException, SAXException
	{		
		//pass in the filepath
		this.filename = filename;
		
		//instantiate an XML reader
		xr = XMLReaderFactory.createXMLReader();
		
		//set event handler and error handler
		ChangeXMLParser handler = new ChangeXMLParser();
		xr.setContentHandler(handler);
		xr.setErrorHandler(handler);
		
		//set up db
		db = new DB();
		
		try
		{
			db.connect();

			db.dropTable("file");
			db.buildTable("file");
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		
		file = new Files();
    	
		//set up changebody, message, target lists
    	fileList = new FileList(db.getPreparedStatement("file"));
    	
    	//for distribution counting
    	changeDistributionMap = new HashMap<Integer, Integer>();
    	targetDistributionMap = new HashMap<Integer, Integer>();
    	messageDistributionMap = new HashMap<Integer, Integer>();
	}
	
	public static Timestamp toTimestamp(String timeStream)
	{
		if (timeStream.length() == 29 || timeStream.length() == 30)
		{
			//for annoying length difference caused by '4' and '11'
			int offset = 0;
			if (timeStream.length() == 29)
			{
				offset = -1;
			}
			
			//the day
			String day = new String(timeStream.substring(8, 10 + offset));
			
			//convert into numerical month
			int month = -1;
			char firstLetter = Character.toUpperCase(timeStream.charAt(4));
			switch(firstLetter)
			{
				case 'J':
				{
					if (Character.toUpperCase(timeStream.charAt(5)) == 'A')
					{
						month = 0;
					}
						else if (Character.toUpperCase(timeStream.charAt(6)) == 'N')
						{
							month = 5;
						}
						else
						{
							month = 6;
						}
					
					break;
				}
				case 'F':
				{
					month = 1;
					
					break;
				}
				case 'M':
				{
					if (Character.toUpperCase(timeStream.charAt(6)) == 'R')
					{
						month = 2;
					}
					else
					{
						month = 4;
					}
					
					break;
				}
				case 'A':
				{
					if (Character.toUpperCase(timeStream.charAt(5)) == 'P')
					{
						month = 3;
					}
					else
					{
						month = 7;
					}
					
					break;
				}
				case 'S':
				{
					month = 8;
					
					break;
				}
				case 'O':
				{
					month = 9;
					
					break;
				}
				case 'N':
				{
					month = 10;
					
					break;
				}
				case 'D':
				{
					month = 11;
					
					break;
				}
				default:
				{
					break;
				}
			}
			
			//year
			String year = new String(timeStream.substring(20 + offset, 24 + offset));
			
			//hour
			String hour = new String(timeStream.substring(11 + offset, 13 + offset));
			
			//minute
			String min = new String(timeStream.substring(14 + offset, 16 + offset));
			
			//second
			String sec = new String(timeStream.substring(17 + offset, 19 + offset));
		
			//sign
			int sign = timeStream.charAt(25 + offset) == '+' ? 1 : -1;
			
			//offset
			String hourOffset = new String(timeStream.substring(26 + offset, 
					28 + offset));
			
			String minOffset = new String(timeStream.substring(28 + offset, 
					30 + offset));
	
			

			int timeOffset = sign * (Integer.parseInt(hourOffset) * 3600000 
					+ Integer.parseInt(minOffset) * 60000);
	
		
			//int timeOffset = 0;
			
			//set up a timezone
			SimpleTimeZone tZone = new SimpleTimeZone(timeOffset, "");
			
			Calendar dayTime = Calendar.getInstance();
			
			dayTime.setTimeZone(tZone);
			
			//set up the time
			dayTime.set(Integer.parseInt(year), month, Integer.parseInt(day),
					Integer.parseInt(hour), Integer.parseInt(min), Integer.parseInt(sec));
	
			/*
			System.out.println("stamp--> " + timeStream);
			System.out.println("hour offset --> " + hourOffset);
			System.out.println("min offset --> " + minOffset);
			System.out.println(dayTime.getTime().toString());
			*/
	
			return new Timestamp(dayTime.getTime().getTime());
			
			/*
			Timestamp tt = new Timestamp(dayTime.get(Calendar.YEAR) - 1900,
					dayTime.get(Calendar.MONTH), dayTime.get(Calendar.DAY_OF_MONTH),
					dayTime.get(Calendar.HOUR), dayTime.get(Calendar.MINUTE),
					dayTime.get(Calendar.SECOND), 0);
			//System.println();
			
			return tt;
			*/
		}
		
		System.err.print("time format--> " + timeStream);
		
		return null;
	}
	
	//write the distribution to file
	private void writeDistributionToFile(String dstFile, 
				HashMap<Integer, Integer> distributionMap) throws IOException
		{
			FileWriter fstream = new FileWriter(dstFile);
			
			BufferedWriter out = new BufferedWriter(fstream);
			
			System.out.println(distributionMap.size() + " different distributions");
			
			//heading distribution count
			out.write("# " + distributionMap.size() + "\n"); 
			
			//iteratively push the suffices into file
			
			Set<Integer> sizeSet = distributionMap.keySet();
			
			int totalNum = 0;
			int totalSize = 0;
			
			for (Integer size: sizeSet)
			{
				int num = distributionMap.get(size);
				
				totalNum += num;
				totalSize += size;
				
				out.write(size + "\t" + num + "\n");  
			}
			
			System.out.println("total num --> " + totalNum);
			System.out.println("total size --> " + totalSize);
			
			out.close();
			
		}

	void getSuffix(String dstFile) throws SQLException, IOException
	{
		HashSet<String> suffix = new HashSet<String>();
		
		String sqlStatement = new String("SELECT filename FROM filename");
		PreparedStatement ps = null;
		
		try
		{
			ps = db.getStreamPreparedStatement(sqlStatement, FILENAME_ROW_LIMIT);
			
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		
		ResultSet rs = ps.executeQuery();
		
		while (rs.next())
		{
			String filename = rs.getString(1);
			
			int dotPosition = filename.lastIndexOf('.');
			
			if (dotPosition >= 0 && dotPosition < filename.length() - 1)
			{
				suffix.add(filename.substring(dotPosition+1, filename.length()));
				
				if (filename.substring(dotPosition+1, filename.length()).equals("g95"))
				{
					System.out.println(filename);
				}
			}
		}
		
		FileWriter fstream = new FileWriter(dstFile);
		
		BufferedWriter out = new BufferedWriter(fstream);
		
		System.out.println(suffix.size() + " different suffices");
		
		//iteratively push the suffices into file
		Iterator it = suffix.iterator();
		
		 while (it.hasNext())
		 {
			  out.write((String)it.next() + "\n");  
		 }
		
		out.close();
	}
	
	//add current size into the distribution map
	private void addMap(int size, HashMap<Integer, Integer> distributionMap)
		{
			//if this size exists, just increase it by 1
			if (distributionMap.containsKey(size))
			{
				int num = distributionMap.get(size);
				
				//need to remove it first, inefficient :(
				distributionMap.remove(size);
				
				distributionMap.put(size, num + 1);
			}
			//else add it into this map, set the num as 1
			else
			{
				distributionMap.put(size, 1);
			}
		}
	
	private void parseXML(String targetDstFile, String messageDstFile, String changeDstFile) throws IOException
	{
		try
		{
			xr.parse(filename);
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		
		writeDistributionToFile(targetDstFile, targetDistributionMap);
		writeDistributionToFile(messageDstFile, messageDistributionMap);
		writeDistributionToFile(changeDstFile, changeDistributionMap);
	}
	
	public static void aaa() throws IOException
	{
		FileInputStream fstream = new FileInputStream("data/aaa.txt");
		  // Get the object of DataInputStream
		  DataInputStream in = new DataInputStream(fstream);
		  BufferedReader br = new BufferedReader(new InputStreamReader(in));
		  String strLine;
		  //Read File Line By Line
		  while ((strLine = br.readLine()) != null)   
		  {
			  String[] xx = strLine.split("\\s");
			  int x1 = Integer.parseInt(xx[0]);
			  int x2 = Integer.parseInt(xx[1]);
			  
			  System.out.println(x1 + "--" + x2);
			  
			  for (int i = 0; i < x2; i++)
			  {
				  System.out.println(x1);
			  }
			  
		  }
	}
	
	
	public static void main(String[] args) throws ClassNotFoundException, 
		IllegalAccessException, SQLException, IOException, SAXException
	{
		//DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
		//Date date = new Date();
		//System.out.println(dateFormat.format(date));
		
		//STBD stbd = new STBD("data/git.log.xml");
	
		//stbd.parseXML("data/targetDistribution.txt", "data/messageDistribution.txt","data/changeDistribution.txt");
		
		//date = new Date();
		//System.out.println(dateFormat.format(date));
		
	
		STBD.aaa();
	}
}
